package org.ben.forkjoin;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
import java.util.concurrent.TimeUnit;

public class FibonacciGenerator {
    public static void main(String[] args) {
        int i = 0;
        while (i++ < 10) {
            Map<String, BigInteger> resultMap;
            NumGeneratorTask<BigInteger> nTask = new NumGeneratorTask<BigInteger>(10000, new BigInteger("0"), new BigInteger("1"),
                    new NumGeneratorTask.BigIntegerGroupStrategy());
            ForkJoinPool fkp = new ForkJoinPool();
            long curt = System.currentTimeMillis();
            fkp.execute(nTask);
            while (!nTask.isDone())
                ;
            fkp.shutdown();
            try {
                fkp.awaitTermination(1, TimeUnit.DAYS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(System.currentTimeMillis() - curt + " , " + nTask.getResultSize());
//            nTask.printKey();
//            try {
//                TimeUnit.SECONDS.sleep(2);
//            } catch (InterruptedException e) {
//                // TODO Auto-generated catch block
//                e.printStackTrace();
//            }
        }
    }
}

class NumGeneratorTask<T extends Number> extends RecursiveTask<T> {
    private static final long serialVersionUID = -4653649422593785922L;
    private int n;
    private T f0;
    private T f1;
    private GroupStrategy<T> gs;
    private static volatile Map<String,Number> RESULTSMAP;
    private T result;

    NumGeneratorTask(int n, T f0, T f1, GroupStrategy<T> gs) {
        this.n = n;
        this.f0 = f0;
        this.f1 = f1;
        this.gs = gs;
        if(null == RESULTSMAP)
            RESULTSMAP = (Map<String, Number>) this.gs.prepareResultMap();
    }

    @Override
    protected T compute() {
//        System.out.println(Thread.currentThread().getName());
        if(RESULTSMAP.containsKey("F("+this.n+")"))
            return (T) RESULTSMAP.get("F("+this.n+")");

        if(0 == n) {
            RESULTSMAP.put("F(0)", this.f0);
//            System.out.println("F(0) = " + this.f0);
            return this.f0;
        } else if(1 == n) {
            RESULTSMAP.put("F(1)", this.f1);
//            System.out.println("F(1) = " + this.f1);
            return this.f1;
        } else {
//            if(RESULTSMAP.containsKey("F("+this.n+")"))
//                return (T) RESULTSMAP.get("F("+this.n+")");
            NumGeneratorTask<T> nTask1 = new NumGeneratorTask<T>(n-1,this.f0,this.f1,this.gs);
            NumGeneratorTask<T> nTask2 = new NumGeneratorTask<T>(n-2,this.f0,this.f1,this.gs);
            invokeAll(nTask1,nTask2);
//            try {
//                System.out.println("F(" + nTask2.getN() + ") = " + nTask2.get() + " ; " + "F(" + nTask1.getN() + ") = " + nTask1.get());
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            } catch (ExecutionException e) {
//                e.printStackTrace();
//            }
            try {
                result = groupResult(nTask2.get(),nTask1.get());
//                System.out.println("F("+this.n+") = " + this.result);
                RESULTSMAP.put("F("+this.n+")", this.result);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    int getN() {
        return this.n;
    }

    T groupResult(T f0, T f1) {
        return gs.groupNum(f0, f1);
    }

    int getResultSize() {
        return RESULTSMAP.entrySet().size();
    }

    void printKey() {
        int i = 0;
//        Iterator<String> it = new TreeSet<String>(RESULTSMAP.keySet()).iterator();
        Iterator<String> it = RESULTSMAP.keySet().iterator();
        while(it.hasNext()) {
            if(i++ >= 50) {
                System.out.println();
                i = 0;
            }
            System.out.print(it.next()+",");
        }
        System.out.println();
    }

    static abstract class GroupStrategy<T> {
        abstract T groupNum(T f0, T f1);
        abstract Map<String,T> prepareResultMap();
    }

    static class IntegerGroupStrategy extends GroupStrategy<Integer> {
        @Override
        Integer groupNum(Integer f0, Integer f1) {
            return f0 + f1;
        }

        @Override
        Map<String, Integer> prepareResultMap() {
            return new HashMap<String,Integer>();
        }
    }

    static class DoubleGroupStrategy extends GroupStrategy<Double> {
        MathContext mc = new MathContext(4);
        BigDecimal bd = new BigDecimal(0,mc);

        @Override
        Double groupNum(Double f0, Double f1) {
            return BigDecimal.valueOf(f0).add(BigDecimal.valueOf(f1)).doubleValue();
        }

        @Override
        Map<String, Double> prepareResultMap() {
            return null;
        }
    }

    static class FloatGroupStrategy extends GroupStrategy<Float> {
        MathContext mc = new MathContext(4);
        BigDecimal bd = new BigDecimal(0,mc);

        @Override
        Float groupNum(Float f0, Float f1) {
            return BigDecimal.valueOf(f0).add(BigDecimal.valueOf(f1)).floatValue();
        }

        @Override
        Map<String, Float> prepareResultMap() {
            return null;
        }
    }

    static class LongGroupStrategy extends GroupStrategy<Long> {
        @Override
        Long groupNum(Long f0, Long f1) {
            return f0 + f1;
        }

        @Override
        Map<String, Long> prepareResultMap() {
            return new HashMap<String,Long>();
        }
    }

    static class BigIntegerGroupStrategy extends GroupStrategy<BigInteger> {
        @Override
        BigInteger groupNum(BigInteger f0, BigInteger f1) {
            return f0.add(f1);
        }

        @Override
        Map<String, BigInteger> prepareResultMap() {
            return new HashMap<String,BigInteger>();
        }
    }
}
